/**
 * Copyright 2009-2011 the original author or authors.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package net.myjung.rest;

import java.io.PrintWriter;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import net.myjung.Myjung;
import net.myjung.model.Query;
import net.myjung.model.util.IdGenerator;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.web.context.support.WebApplicationContextUtils;

/**
 * @author Steve M. Jung
 */
public class RestServlet extends AbstractRestServlet {
	private static final long serialVersionUID = 1L;

	protected static Log logger = LogFactory.getLog(RestServlet.class);

	private String objectName;
	private Class<?> clazz;
	private String classId;
	private Field idf;
	private String factoryId;
	private Object manager;
	private Method getLister;
	private Method getter;
	private Method setter;
	private Method creator;
	private Method updater;
	private Method deleter;

	public RestServlet() {
		super();
	}

	@Override
	protected List<?> getList(HttpServletRequest request, HttpServletResponse response, Query conds, Map<String, Object> options) throws Exception {
		preload();
		return (List<?>) getGetLister().invoke(getManager(), conds, options);
	}
	@Override
	protected Object get(HttpServletRequest request, HttpServletResponse response, String id, Map<String, Object> options) throws Exception {
		preload();
		if (options == null)
			options = new HashMap<String, Object>();
		return getGetter().invoke(getManager(), id, options);
	}
	private Object populate(Object obj, Map<String, Object> options) throws Exception {
		String level = (String) options.get("level");
		if (Myjung.LEVEL_ALL.equals(level))
			return obj;
		if (idf == null) {
			idf = IdGenerator.getIdField(obj.getClass());
			if (!idf.isAccessible())
				idf.setAccessible(true);
		}
		String id = (String) idf.get(obj);
		Object oldObj = getGetter().invoke(getManager(), id, null);
		if (oldObj == null)
			return obj;
		Myjung.populate(obj, oldObj, level);
		obj = oldObj;
		return obj;
	}
	@Override
	protected Object set(HttpServletRequest request, HttpServletResponse response, String username, Object obj, Map<String, Object> options)
			throws Exception {
		preload();
		obj = populate(obj, options);
		getSetter().invoke(getManager(), username, obj, options);
		return obj;
	}
	@Override
	protected Object create(HttpServletRequest request, HttpServletResponse response, String username, Object obj, Map<String, Object> options)
			throws Exception {
		preload();
		getCreator().invoke(getManager(), username, obj, options);
		return obj;
	}
	@Override
	protected Object update(HttpServletRequest request, HttpServletResponse response, String username, Object obj, Map<String, Object> options)
			throws Exception {
		preload();
		obj = populate(obj, options);
		getUpdater().invoke(getManager(), username, obj, options);
		return obj;
	}
	@Override
	protected Object delete(HttpServletRequest request, HttpServletResponse response, String id, Map<String, Object> options) throws Exception {
		preload();
		return getDeleter().invoke(getManager(), id, options);
	}

	private boolean preloaded = false;
	private Object monitor = new Object();
	private void preload() throws ServletException {
		if (preloaded)
			return;
		synchronized (monitor) {
			if (preloaded)
				return;
			objectName = getInitParameter("objectName");
			classId = getInitParameter("classId");
			factoryId = getInitParameter("factoryId");
			if (Myjung.isEmpty(objectName) || Myjung.isEmpty(classId) || Myjung.isEmpty(factoryId))
				throw new ServletException("Init parameters are not enough to load.");
		}
	}
	private Object getManager() {
		if (manager == null) {
			try {
				manager = WebApplicationContextUtils.getWebApplicationContext(getServletContext()).getBean(factoryId).getClass()
						.getMethod("getManager").invoke(null);
			} catch (Exception e) {
				logger.error(e, e);
			}
		}
		return manager;
	}
	private Method getGetLister() {
		if (getLister == null) {
			try {
				Method[] methods = getManager().getClass().getMethods();
				if (!Myjung.isEmpty(methods)) {
					String methodName = new StringBuffer("get").append(objectName).append("List").toString();
					for (Method method : methods) {
						if (!method.getName().equals(methodName))
							continue;
						getLister = method;
						break;
					}
				}
			} catch (Exception e) {
				logger.error(e, e);
			}
		}
		return getLister;
	}
	private Method getGetter() {
		if (getter == null) {
			try {
				Method[] methods = getManager().getClass().getMethods();
				if (!Myjung.isEmpty(methods)) {
					String methodName = new StringBuffer("get").append(objectName).toString();
					for (Method method : methods) {
						if (!method.getName().equals(methodName))
							continue;
						getter = method;
						break;
					}
				}
			} catch (Exception e) {
				logger.error(e, e);
			}
		}
		return getter;
	}
	private Method getSetter() {
		if (setter == null) {
			try {
				Method[] methods = getManager().getClass().getMethods();
				if (!Myjung.isEmpty(methods)) {
					String methodName = new StringBuffer("set").append(objectName).toString();
					for (Method method : methods) {
						if (!method.getName().equals(methodName))
							continue;
						setter = method;
						break;
					}
				}
			} catch (Exception e) {
				logger.error(e, e);
			}
		}
		return setter;
	}
	private Method getCreator() {
		if (creator == null) {
			try {
				Method[] methods = getManager().getClass().getMethods();
				if (!Myjung.isEmpty(methods)) {
					String methodName = new StringBuffer("create").append(objectName).toString();
					for (Method method : methods) {
						if (!method.getName().equals(methodName))
							continue;
						creator = method;
						break;
					}
				}
			} catch (Exception e) {
				logger.error(e, e);
			}
		}
		return creator;
	}
	private Method getUpdater() {
		if (updater == null) {
			try {
				Method[] methods = getManager().getClass().getMethods();
				if (!Myjung.isEmpty(methods)) {
					String methodName = new StringBuffer("update").append(objectName).toString();
					for (Method method : methods) {
						if (!method.getName().equals(methodName))
							continue;
						updater = method;
						break;
					}
				}
			} catch (Exception e) {
				logger.error(e, e);
			}
		}
		return updater;
	}
	private Method getDeleter() {
		if (deleter == null) {
			try {
				Method[] methods = getManager().getClass().getMethods();
				if (!Myjung.isEmpty(methods)) {
					String methodName = new StringBuffer("delete").append(objectName).toString();
					for (Method method : methods) {
						if (!method.getName().equals(methodName))
							continue;
						deleter = method;
						break;
					}
				}
			} catch (Exception e) {
				logger.error(e, e);
			}
		}
		return deleter;
	}
	@Override
	protected Object toObject(String xml) throws Exception {
		return toObject(xml, null);
	}
	private Object toObject(String xml, Object obj) throws Exception {
		try {
			if (clazz == null) {
				preload();
				clazz = WebApplicationContextUtils.getWebApplicationContext(getServletContext()).getBean(classId).getClass();
			}
			return clazz.getMethod("toObject", String.class).invoke(obj == null ? clazz.newInstance() : obj, xml);
		} catch (Exception e) {
			throw e;
		}
	}

	@Override
	protected void action(HttpServletRequest request, HttpServletResponse response, PrintWriter out) throws Exception {
	}
}
